package Derivation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import Basics.*;

public class ParseResultsTopDown 
{
	DeriveByProgramTopDown programDeriver;
	
	Vector<Atom> atomsToDerive = new Vector<Atom>();
	
	Map<Atom, Vector<Vector<Atom>>> provenance = new HashMap<Atom, Vector<Vector<Atom>>>();
	
	
	
	public ParseResultsTopDown (Rule ... irs)
	{
		this.programDeriver = new DeriveByProgramTopDown (irs);
	}
	
	
	
	public ParseResultsTopDown (Program p)
	{
		this.programDeriver = new DeriveByProgramTopDown (p);
	}
	
	
	
	public DeriveByProgramTopDown getProgramDeriver() 
	{
		return programDeriver;
	}



	public void setProgramDeriver(DeriveByProgramTopDown programDeriver) 
	{
		this.programDeriver = programDeriver;
	}



	public Vector<Atom> getAtomsToDerive() 
	{
		return atomsToDerive;
	}



	public void setAtomsToDerive(Vector<Atom> atomsToDerive) 
	{
		this.atomsToDerive = atomsToDerive;
	}



	public Map<Atom, Vector<Vector<Atom>>> getProvenance() 
	{
		return provenance;
	}



	public void setProvenance(Map<Atom, Vector<Vector<Atom>>> provenance) 
	{
		this.provenance = provenance;
	}

	
	
	/*************************************************************************************************************/
	/** Title: ClearProvenanceFromIrrelevantBodies																				
	/** Description: clears the prov. vector from atoms that cannot be derived		
	/*************************************************************************************************************/
	
	public void ClearProvenanceFromIrrelevantBodies()
	{
		Map<Atom, Vector<Vector<Atom>>> toBeRemoved = new HashMap<Atom, Vector<Vector<Atom>>> ();
		for (Atom key : this.provenance.keySet()) 
		{
			Vector<Vector<Atom>> bodies = new Vector<Vector<Atom>>();
			for (Vector<Atom> body : this.provenance.get(key)) 
			{
				for (Atom atom : body) 
				{
					if (true == IsNotDerivable(atom))
					{
						bodies.add(body);
					}
				}
			}
			
			toBeRemoved.put(key, bodies);
		}
		
		for (Atom key : toBeRemoved.keySet()) 
		{
			this.provenance.get(key).removeAll(toBeRemoved.get(key));
		}
	}

	
	
	/*************************************************************************************************************/
	/** Title: HasBeenDerived																				
	/** Description: Checks if atom is already in prov. or atom is already in DB  				
	/*************************************************************************************************************/
	
	public boolean HasBeenDerived (Atom atom)
	{
		//Why containsKey(atom) or keySet().contains(atom) do not work here???
		/*boolean retVal = false;
		for (Atom key : this.provenance.keySet()) 
		{
			if(key.equals(atom))
			{
				retVal = true;
			}
		}*/
		
		return this.provenance.containsKey(atom) || DB.getInstance().ContainedInTable(atom);
	}
	
	
	/*************************************************************************************************************/
	/** Title: IsNotDeriveble																				
	/** Description: Checks if atom cannot be derived  				
	/*************************************************************************************************************/
	
	public boolean IsNotDerivable (Atom atom)
	{
		return !DB.getInstance().ContainedInTable(atom) && atom.IsAtomRelationEdb(this.programDeriver.p);
	}
	
	
	/*************************************************************************************************************/
	/** Title: AddToAtomsNeedToBeDerived																				
	/** Description: select atoms that need to be derived and add them to vector		
	/*************************************************************************************************************/
	
	public void AddToAtomsNeedToBeDerived (Vector<Vector<Atom>> bodiesOfPartlyInstRules)
	{
		for (Vector<Atom> vector : bodiesOfPartlyInstRules) 
		{
			for (Atom atom : vector) 
			{
				boolean needToBeAdded = true;				
				if (true == atom.IsAtomRelationEdb(this.programDeriver.p)) //atom cannot be derived 
				{
					needToBeAdded = false;
				}
				
				if (true == HasBeenDerived(atom))
				{
					needToBeAdded = false;
				}
				
				if (true == this.atomsToDerive.contains(atom)) //atom is already in list
				{
					needToBeAdded = false;
				}
				
				if (true == needToBeAdded) 
				{
					this.atomsToDerive.add(atom);
				}
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: getRelevantProvFromAllDerivations																				
	/** Description: get the derivations relevant to derive this specific partlyInstAtom			
	/*************************************************************************************************************/
	
	public Vector<Vector<Atom>> getProv(Atom instAtom, Vector<Vector<Rule>> derivationsVec)
	{
		Vector<Vector<Atom>> prov = new Vector<Vector<Atom>>();
		for (Vector<Rule> vector : derivationsVec) //add rules that have head that match the desired atom
		{
			for (Rule rule : vector) 
			{
				prov.add(rule.getBody());
			}
		}
		
		if (DB.getInstance().ContainedInTable(instAtom)) 
		{
			Vector<Atom> fact = new Vector<Atom> ();
			fact.add(instAtom);
			prov.add(fact);
		}
		
		return prov;
	}
	

	
	/*************************************************************************************************************/
	/** Title: LeaveOnlyPossibleDerivations																				
	/** Description: clears the final prov. vector from atoms that cannot be derived		
	/*************************************************************************************************************/
	
	public void LeaveOnlyPossibleDerivations()
	{
		Map<Atom, Vector<Vector<Atom>>> toBeRemoved = new HashMap<Atom, Vector<Vector<Atom>>> ();
		int markedAtCurrentStep = 1;
		
		while (markedAtCurrentStep > 0)
		{
			markedAtCurrentStep = MarkAtomsWithStableDerivation();
		}
		
		
		for (Atom key : this.provenance.keySet()) 
		{
			Vector<Vector<Atom>> bodies = new Vector<Vector<Atom>>();
			for (Vector<Atom> body : this.provenance.get(key)) 
			{
				for (Atom atom : body) 
				{
					if (false == this.provenance.containsKey(atom) && true == IsNotDerivable(atom))
					{
						bodies.add(body);
					}
					
					if (false == bodies.contains(body) && false == atom.isStable()) 
					{
						bodies.add(body);
					}
				}
			}
			
			toBeRemoved.put(key, bodies);
		}
		
		for (Atom key : toBeRemoved.keySet()) 
		{
			this.provenance.get(key).removeAll(toBeRemoved.get(key));
			if (this.provenance.get(key).isEmpty()) 
			{
				this.provenance.remove(key);
			}
		}
	}

	
	
	/*************************************************************************************************************/
	/** Title: LeaveOnlyRelevantDerivations																				
	/** Description: clears the final prov. vector from atoms that are not used in the derivation of root		
	/*************************************************************************************************************/
	
	public void LeaveOnlyRelevantDerivations (Atom root)
	{
		List<Atom> toBeRemoved = new ArrayList<Atom>();
		removeIrrelevantProv(root);
		for (Atom key : this.provenance.keySet()) 
		{
			if (false == key.isRelevantForDerivationTopDown()) 
			{
				toBeRemoved.add(key);
			}
		}
		
		for (Atom atom : toBeRemoved) 
		{
			this.provenance.remove(atom);
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: IsDerivationStable																				
	/** Description: Checks if the atom can be derived from the DB and program or just has an endless cycle in the prov.		
	/*************************************************************************************************************/
	
	public void HasStableDerivation (Atom atom)
	{
		if (DB.getInstance().ContainedInTable(atom)) 
		{
			atom.setStable(true);
		}
		
		if (false == atom.isStable()) 
		{
			for (Vector<Atom> vector : this.provenance.get(atom)) 
			{
				boolean isBodyStable = true;
				for (Atom bodyAtom : vector) 
				{
					if (false == bodyAtom.isStable()) 
					{
						isBodyStable = false;
					}
				}
				
				if (true == isBodyStable) //the entire vector is consisted of stable atoms 
				{
					atom.setStable(true);
					break;
				}
				
				isBodyStable = true;
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkAtomsWithStableDerivation																				
	/** Description: Checks if the atoms in prov. can be derived from the DB and program or just have an endless cycle in the prov.		
	/*************************************************************************************************************/
	
	public int MarkAtomsWithStableDerivation ()
	{
		int markedAtCurrentStep = 0;
		for (Atom key : this.provenance.keySet()) 
		{
			for (Vector<Atom> vector : this.provenance.get(key)) 
			{
				for (Atom atom : vector) 
				{
					boolean wasStableBefore = atom.isStable();
					HasStableDerivation(atom);
					if (atom.isStable() != wasStableBefore) 
					{
						markedAtCurrentStep++;
					}
				}
			}
		}
		
		return markedAtCurrentStep;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkRelevantProv																				
	/** Description: Marks all atoms that derive this root		
	/*************************************************************************************************************/
	
	public void MarkRelevantProv (List<Atom> queue)
	{
		Atom toMark = queue.remove(0);
		toMark.setRelevantForDerivationTopDown(true);
		for (Atom key : this.provenance.keySet()) 
		{
			if (key.equals(toMark)) 
			{
				key.setRelevantForDerivationTopDown(true);
				for (Vector<Atom> vector : this.provenance.get(key)) 
				{
					for (Atom atom : vector) 
					{
						if (false == atom.isRelevantForDerivationTopDown()) 
						{
							queue.add(atom);
						}
					}
				}
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: RemoveIrrelevantProv																				
	/** Description: removes all atoms that aren't relevant for the derivation tree of this fact		
	/*************************************************************************************************************/
	
	public void removeIrrelevantProv (Atom root)
	{		
		List<Atom> queue = new ArrayList<Atom>();
		queue.add(root);
		
		while (false == queue.isEmpty())
		{
			MarkRelevantProv(queue);
		}
	}
}
